package com.itaming.lycheeframework.support.utils;

import com.itaming.lycheeframework.common.constant.FrameworkConstant;
import com.itaming.lycheeframework.support.datetime.ThreadSafeDateFormatFactory;
import lombok.experimental.UtilityClass;

import java.text.DateFormat;
import java.text.ParseException;
import java.time.*;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;

/**
 * Date工具类
 *
 * @author A.Ming
 */
@UtilityClass
public class DateUtil {

    /**
     * DateFormat
     */
    public static final ThreadLocal<DateFormat> DATETIME_FORMATTER = ThreadSafeDateFormatFactory.getDateFormat(FrameworkConstant.DATETIME_FORMAT);
    public static final ThreadLocal<DateFormat> DATE_FORMATTER = ThreadSafeDateFormatFactory.getDateFormat(FrameworkConstant.DATE_FORMAT);
    public static final ThreadLocal<DateFormat> TIME_FORMATTER = ThreadSafeDateFormatFactory.getDateFormat(FrameworkConstant.TIME_FORMAT);

    /**
     * 转为<code>Date</code>
     *
     * @param temporal 日期时间
     * @return 日期时间
     */
    public static Date of(TemporalAccessor temporal) {
        return temporal == null ? null : Date.from(DateTimeUtil.toInstant(temporal));
    }

    /**
     * 转为日期
     *
     * @param calendar 日期时间
     * @return 日期时间
     */
    public static Date of(Calendar calendar) {
        return calendar == null ? null : Date.from(calendar.toInstant());
    }

    /**
     * 转为日期
     *
     * @param epochMilli 纪元毫秒
     * @return 日期时间
     */
    public static Date of(long epochMilli) {
        return of(Instant.ofEpochMilli(epochMilli));
    }

    /**
     * 格式化为日期字符串
     *
     * @param date 日期
     * @return 格式化后的字符串
     */
    public static String format(Date date) {
        return format(date, DATETIME_FORMATTER);
    }

    /**
     * 格式化为日期字符串
     *
     * @param date 日期
     * @return 格式化后的字符串
     */
    public static String formatDate(Date date) {
        return format(date, DATE_FORMATTER);
    }

    /**
     * 格式化为时间字符串
     *
     * @param date 日期
     * @return 格式化后的字符串
     */
    public static String formatTime(Date date) {
        return format(date, TIME_FORMATTER);
    }

    /**
     * 按照指定的格式格式化日期
     *
     * @param date   日期
     * @param format 格式
     * @return 格式化后的字符串
     */
    public static String format(Date date, String format) {
        return format(date, ThreadSafeDateFormatFactory.getDateFormat(format));
    }

    /**
     * 使用指定的格式化器格式化日期
     *
     * @param date      日期
     * @param formatter 格式化器
     * @return 格式化后的字符串
     */
    public static String format(Date date, ThreadLocal<DateFormat> formatter) {
        return date == null || formatter == null ? null : formatter.get().format(date);
    }

    /**
     * 解析日期时间
     *
     * @param dateStr 字符串
     * @return 日期时间
     */
    public static Date parse(String dateStr) {
        return parse(dateStr, DATETIME_FORMATTER);
    }

    /**
     * 解析日期
     *
     * @param dateStr 字符串
     * @return 日期
     */
    public static Date parseDate(String dateStr) {
        return parse(dateStr, DATE_FORMATTER);
    }

    /**
     * 解析时间
     *
     * @param dateStr 字符串
     * @return 日期
     */
    public static Date parseTime(String dateStr) {
        return parse(dateStr, TIME_FORMATTER);
    }

    /**
     * 根据指定的格式解析日期
     *
     * @param dateStr 字符串
     * @param format  格式
     * @return 日期
     */
    public static Date parse(String dateStr, String format) {
        return parse(dateStr, ThreadSafeDateFormatFactory.getDateFormat(format));
    }

    /**
     * 使用指定的格式化器解析日期
     *
     * @param dateStr   字符串
     * @param formatter 格式化器
     * @return 日期
     */
    public static Date parse(String dateStr, ThreadLocal<DateFormat> formatter) {
        if (dateStr == null || formatter == null) {
            return null;
        }

        try {
            return formatter.get().parse(dateStr);
        } catch (ParseException e) {
            throw new IllegalArgumentException(e.getMessage(), e);
        }
    }

    /**
     * 获取时间差
     *
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @return 时间间隔
     */
    public static Duration between(Date startDate, Date endDate) {
        return Duration.between(startDate.toInstant(), endDate.toInstant());
    }

    /**
     * 转为日历
     *
     * @param date 日期
     * @return 日历
     */
    public static Calendar toCalendar(Date date) {
        if (date == null) {
            return null;
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setLenient(false);
        calendar.setTime(date);
        return calendar;
    }

    /**
     * 日期转为纪元秒
     *
     * @param date 日期
     * @return 纪元秒
     */
    public static long toEpochSecond(Date date) {
        return toEpochMilli(date) / 1000;
    }

    /**
     * 日期转为纪元毫秒
     *
     * @param date 日期
     * @return 纪元毫秒
     */
    public static long toEpochMilli(Date date) {
        return date == null ? 0 : date.toInstant().toEpochMilli();
    }

    /**
     * 日期转为<code>Instant</code>
     *
     * @param date 日期
     * @return Instant
     */
    public static Instant toInstant(Date date) {
        return date == null ? null : date.toInstant();
    }

    /**
     * 日期转为<code>LocalDateTime</code>
     *
     * @param date Date
     * @return 日期
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        return date == null ? null : date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    /**
     * 日期转为<code>LocalDate</code>
     *
     * @param date Date
     * @return 日期
     */
    public static LocalDate toLocalDate(Date date) {
        return date == null ? null : date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

    /**
     * 日期转为<code>LocalTime</code>
     *
     * @param date Date
     * @return 时间
     */
    public static LocalTime toLocalTime(Date date) {
        return date == null ? null : date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
    }

    /**
     * 获取当前时间的纪元毫秒
     *
     * @return
     */
    public static long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    /**
     * 获取当前时间的纪元秒
     *
     * @return
     */
    public static long currentTimeSeconds() {
        return System.currentTimeMillis() / 1000L;
    }

    /**
     * 修改时间为<code>00:00:00.0</code>
     *
     * @param date 日期
     * @return 日期时间
     */
    public static Date withStartOfDay(Date date) {
        return withTime(date, 0, 0, 0, 0);
    }

    /**
     * 修改时间为<code>23:59:59.999</code>
     *
     * @param date 日期
     * @return 日期
     */
    public static Date withEndOfDay(Date date) {
        return withTime(date, 23, 59, 59, 999);
    }

    /**
     * 修改时间
     *
     * @param date        日期时间
     * @param hour        小时
     * @param minute      分
     * @param second      秒
     * @param millisecond 毫秒
     * @return 日期时间
     */
    public static Date withTime(Date date, int hour, int minute, int second, int millisecond) {
        if (date == null) {
            return null;
        }
        Calendar calendar = toCalendar(date);
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, millisecond);
        return calendar.getTime();
    }

    /**
     * 加年
     *
     * @param date  日期
     * @param years 年数
     * @return 日期
     */
    public static Date plusYears(Date date, int years) {
        return plus(date, years, Calendar.YEAR);
    }

    /**
     * 加月
     *
     * @param date   日期
     * @param months 月数
     * @return 日期
     */
    public static Date plusMonths(Date date, int months) {
        return plus(date, months, Calendar.MONTH);
    }

    /**
     * 加天
     *
     * @param date 日期
     * @param days 天数
     * @return 日期
     */
    public static Date plusDays(Date date, int days) {
        return plus(date, days, Calendar.DAY_OF_MONTH);
    }

    /**
     * 加小时
     *
     * @param date  日期
     * @param hours 小时数
     * @return 日期
     */
    public static Date plusHours(Date date, int hours) {
        return plus(date, hours, Calendar.HOUR_OF_DAY);
    }

    /**
     * 加分钟
     *
     * @param date    日期时间
     * @param minutes 分钟数
     * @return Temporal
     */
    public static Date plusMinutes(Date date, int minutes) {
        return plus(date, minutes, Calendar.MINUTE);
    }

    /**
     * 加秒
     *
     * @param date    日期时间
     * @param seconds 秒数
     * @return Temporal
     */
    public static Date plusSeconds(Date date, int seconds) {
        return plus(date, seconds, Calendar.SECOND);
    }

    /**
     * 加时间
     *
     * @param date          时间
     * @param calendarField 日历字段
     * @param amount        数量
     * @return 设置后的时间
     */
    private static Date plus(Date date, int amount, int calendarField) {
        if (date == null) {
            return null;
        }
        Calendar c = toCalendar(date);
        c.add(calendarField, amount);
        return c.getTime();
    }

    /**
     * 加毫秒
     *
     * @param date   日期时间
     * @param millis 毫秒数
     * @return Temporal
     */
    public static Date plusMillis(Date date, int millis) {
        return plus(date, millis, Calendar.MILLISECOND);
    }

    /**
     * 减年
     *
     * @param date  日期
     * @param years 年数
     * @return 日期
     */
    public static Date minusYears(Date date, int years) {
        return minus(date, years, Calendar.YEAR);
    }

    /**
     * 减月
     *
     * @param date   日期
     * @param months 月数
     * @return 日期
     */
    public static Date minusMonths(Date date, int months) {
        return minus(date, months, Calendar.MONTH);
    }

    /**
     * 减天
     *
     * @param date 日期
     * @param days 天数
     * @return 日期
     */
    public static Date minusDays(Date date, int days) {
        return minus(date, days, Calendar.DAY_OF_MONTH);
    }

    /**
     * 减小时
     *
     * @param date  日期
     * @param hours 小时数
     * @return 日期
     */
    public static Date minusHours(Date date, int hours) {
        return minus(date, hours, Calendar.HOUR_OF_DAY);
    }

    /**
     * 减分钟
     *
     * @param date    日期时间
     * @param minutes 分钟数
     * @return Temporal
     */
    public static Date minusMinutes(Date date, int minutes) {
        return minus(date, minutes, Calendar.MINUTE);
    }

    /**
     * 减秒
     *
     * @param date    日期时间
     * @param seconds 秒数
     * @return Temporal
     */
    public static Date minusSeconds(Date date, int seconds) {
        return minus(date, seconds, Calendar.SECOND);
    }

    /**
     * 减毫秒
     *
     * @param date   日期时间
     * @param millis 毫秒数
     * @return Temporal
     */
    public static Date minusMillis(Date date, int millis) {
        return minus(date, millis, Calendar.MILLISECOND);
    }

    /**
     * 减时间
     *
     * @param date          时间
     * @param calendarField 日历字段
     * @param amount        数量
     * @return 设置后的时间
     */
    private static Date minus(Date date, int amount, int calendarField) {
        return amount == Integer.MIN_VALUE
            ? plus(plus(date, Integer.MAX_VALUE, calendarField), 1, calendarField)
            : plus(date, -amount, calendarField);
    }

    /**
     * 是否为周末
     *
     * @param date 日期
     * @return boolean
     */
    public static boolean isWeekend(Date date) {
        if (date == null) {
            return false;
        }

        Calendar calendar = toCalendar(date);
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
        return Calendar.SATURDAY == dayOfWeek || Calendar.SUNDAY == dayOfWeek;
    }

    /**
     * 是否为同一天
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return boolean
     */
    public static boolean isSameDay(Date date1, Date date2) {
        if (date1 == null || date2 == null) {
            return false;
        }

        Calendar c1 = toCalendar(date1);
        Calendar c2 = toCalendar(date2);

        return c1.get(Calendar.ERA) == c2.get(Calendar.ERA)
            && c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
            && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
    }

    /**
     * 是否为同一时间
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return boolean
     */
    public static boolean isSameTime(Date date1, Date date2) {
        return date1 != null && date1.equals(date2);
    }

    /**
     * 日期是否在区间内
     *
     * @param date      日期时间
     * @param beginDate 开始日期时间
     * @param endDate   结束日期时间
     * @return boolean
     */
    public static boolean isIn(Date date, Date beginDate, Date endDate) {
        return isIn(date, beginDate, endDate, true, true);
    }

    /**
     * 日期是否在区间内
     *
     * @param date         日期时间
     * @param beginDate    开始日期时间
     * @param endDate      结束日期时间
     * @param includeBegin 是否包含开始
     * @param includeEnd   是否包含结束
     * @return boolean
     */
    public static boolean isIn(Date date, Date beginDate, Date endDate, boolean includeBegin, boolean includeEnd) {
        if (date == null || beginDate == null || endDate == null) {
            return false;
        }

        long thisMills = toEpochMilli(date);
        long beginMills = toEpochMilli(beginDate);
        long endMills = toEpochMilli(endDate);
        long rangeMin = Math.min(beginMills, endMills);
        long rangeMax = Math.max(beginMills, endMills);
        boolean isIn = rangeMin < thisMills && thisMills < rangeMax;
        if (!isIn && includeBegin) {
            isIn = thisMills == rangeMin;
        }

        if (!isIn && includeEnd) {
            isIn = thisMills == rangeMax;
        }

        return isIn;
    }

}
